commonlibsse_ng\re\m\MemoryManager\alloc\tes_global/
global.rs

1// SPDX-FileCopyrightText: (c) The Rust Project Contributors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// - https://github.com/rust-lang/rust/blob/master/LICENSE-MIT
4//
5//! Rust's Allocator compatible memory allocator for Skyrim.
6use core::alloc::GlobalAlloc;
7use core::ptr;
8use core::{alloc::Layout, hint, ptr::NonNull};
9
10use stdx::alloc::{AllocError, Allocator, non_null_empty_slice};
11
12use crate::re::MemoryManager::alloc::{alloc, dealloc, realloc};
13
14/// The Skyrim global memory allocator using `MemoryManager` C++ class.
15///
16/// It implements [`Allocator`] and [`GlobalAlloc`], so it can be used for `#[global_allocator]` and other Allocator changeable arrays.
17///
18/// Note: while this type is unstable, the functionality it provides can be
19/// accessed through the [free functions in `alloc`](self#functions).
20///
21/// # CI
22/// Skyrim `MemoryManager` is not available for CI,
23/// Therefore, the `test_on_ci` feature is enabled, it will automatically fall back to Rust's [`Global`](std::alloc::Global).
24#[derive(Debug, Default, Clone, PartialEq)]
25pub struct TESGlobalAlloc;
26
27unsafe impl Allocator for TESGlobalAlloc {
28    #[inline]
29    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
30    fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
31        Self::alloc_impl(layout, false)
32    }
33
34    #[inline]
35    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
36    fn allocate_zeroed(&self, layout: Layout) -> Result<NonNull<[u8]>, AllocError> {
37        Self::alloc_impl(layout, true)
38    }
39
40    #[inline]
41    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
42    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
43        if layout.size() != 0 {
44            // SAFETY: `layout` is non-zero in size,
45            // other conditions must be upheld by the caller
46            unsafe { dealloc(ptr.as_ptr(), layout) }
47        }
48    }
49
50    #[inline]
51    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
52    unsafe fn grow(
53        &self,
54        ptr: NonNull<u8>,
55        old_layout: Layout,
56        new_layout: Layout,
57    ) -> Result<NonNull<[u8]>, AllocError> {
58        // SAFETY: all conditions must be upheld by the caller
59        unsafe { Self::grow_impl(ptr, old_layout, new_layout, false) }
60    }
61
62    #[inline]
63    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
64    unsafe fn grow_zeroed(
65        &self,
66        ptr: NonNull<u8>,
67        old_layout: Layout,
68        new_layout: Layout,
69    ) -> Result<NonNull<[u8]>, AllocError> {
70        // SAFETY: all conditions must be upheld by the caller
71        unsafe { Self::grow_impl(ptr, old_layout, new_layout, true) }
72    }
73
74    #[inline]
75    #[cfg_attr(miri, track_caller)] // even without panics, this helps for Miri backtraces
76    unsafe fn shrink(
77        &self,
78        ptr: NonNull<u8>,
79        old_layout: Layout,
80        new_layout: Layout,
81    ) -> Result<NonNull<[u8]>, AllocError> {
82        debug_assert!(
83            new_layout.size() <= old_layout.size(),
84            "`new_layout.size()` must be smaller than or equal to `old_layout.size()`"
85        );
86
87        match new_layout.size() {
88            // SAFETY: conditions must be upheld by the caller
89            0 => {
90                unsafe { self.deallocate(ptr, old_layout) };
91                Ok(non_null_empty_slice(new_layout))
92            }
93
94            // SAFETY: `new_size` is non-zero. Other conditions must be upheld by the caller
95            new_size if old_layout.align() == new_layout.align() => unsafe {
96                // `realloc` probably checks for `new_size <= old_layout.size()` or something similar.
97                hint::assert_unchecked(new_size <= old_layout.size());
98
99                let raw_ptr = realloc(ptr.as_ptr(), old_layout, new_size);
100                let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
101                Ok(NonNull::slice_from_raw_parts(ptr, new_size))
102            },
103
104            // SAFETY: because `new_size` must be smaller than or equal to `old_layout.size()`,
105            // both the old and new memory allocation are valid for reads and writes for `new_size`
106            // bytes. Also, because the old allocation wasn't yet deallocated, it cannot overlap
107            // `new_ptr`. Thus, the call to `copy_nonoverlapping` is safe. The safety contract
108            // for `dealloc` must be upheld by the caller.
109            new_size => unsafe {
110                let new_ptr = self.allocate(new_layout)?;
111                ptr::copy_nonoverlapping(ptr.as_ptr(), new_ptr.cast().as_ptr(), new_size);
112                self.deallocate(ptr, old_layout);
113                Ok(new_ptr)
114            },
115        }
116    }
117}
118
119unsafe impl GlobalAlloc for TESGlobalAlloc {
120    #[inline]
121    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
122        unsafe { alloc(layout) }
123    }
124
125    #[inline]
126    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
127        unsafe { dealloc(ptr, layout) }
128    }
129
130    #[inline]
131    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
132        unsafe { realloc(ptr, layout, new_size) }
133    }
134}